package org.msb.accentis.gui.views;

import static java.awt.GridBagConstraints.BOTH;
import static java.awt.GridBagConstraints.CENTER;
import static java.awt.GridBagConstraints.EAST;
import static java.awt.GridBagConstraints.NONE;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;

import org.msb.accentis.data.DataManagerFactory;
import org.msb.accentis.data.IDataManager;
import org.msb.accentis.data.pojo.Account;
import org.msb.accentis.data.pojo.Transaction;
import org.msb.accentis.data.pojo.TransactionDetail;
import org.msb.accentis.gui.InsetsConstants;
import org.msb.accentis.gui.Resources;
import org.msb.accentis.gui.swing.ActionResourceDefinition;
import org.msb.accentis.gui.swing.MSBAction;
import org.msb.accentis.gui.swing.ValidatingTabbedPane;

/**
 * The {@code TransactionEditor} class is an object that controls the editing of {@link Transaction} objects. It dispatches the proper messages to the view
 * components that present the user interface to edit an existing or new {@link Transaction} object.
 * 
 */
public class TransactionEditor extends JComponent {

	private static int SIMPLE_TRANSACTION_TAB = 0;

	private static int TRANSFER_TRANSACTION_TAB = 1;

	private static int COMPLEX_TRANSACTION_TAB = 2;

	private Transaction transaction;

	private boolean newTransaction;

	private boolean dirty;

	private boolean editing;

	private Account account;

	private JTabbedPane transactionEditorViewContainer;

	private Action newAction;

	private Action editAction;

	private Action enterAction;

	private Action cancelAction;

	private ActionListener actionListener;

	public TransactionEditor() {
		createComponents();
	}

	private void createComponents() {
		setLayout(new GridBagLayout());

		add(getTransactionEditorViewContainer(), new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, CENTER, BOTH, InsetsConstants.ZERO_INSETS, 0, 0));
		add(createButtonPanel(), new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, EAST, NONE, InsetsConstants.FIVE_ON_TOP_INSETS, 0, 0));
	}

	protected JTabbedPane getTransactionEditorViewContainer() {
		if (null == transactionEditorViewContainer)
			createTransactionEditorViewContainer();

		return transactionEditorViewContainer;
	}

	private void createTransactionEditorViewContainer() {
		transactionEditorViewContainer = new ValidatingTabbedPane(new ValidatingTabbedPane.Validator() {
			@Override
			public boolean canSwitchTabs(int newTabIndex) {
				if (COMPLEX_TRANSACTION_TAB == transactionEditorViewContainer.getSelectedIndex()) {
					if (SIMPLE_TRANSACTION_TAB == newTabIndex) {
						if (1 < transaction.getTransactionDetails().size()) {
							JOptionPane.showMessageDialog(TransactionEditor.this,
									"A transaction with more than one transaction detail cannot be converted into a simple transaction.");

							return false;
						}
					} else if (TRANSFER_TRANSACTION_TAB == newTabIndex) {
						if (2 < transaction.getTransactionDetails().size()) {
							JOptionPane.showMessageDialog(TransactionEditor.this,
									"A transaction with more than two transaction details cannot be converted into a transfer transaction.");

							return false;
						} else if (2 == transaction.getTransactionDetails().size()) {
							TransactionDetail detail1 = transaction.getTransactionDetails().get(0);
							TransactionDetail detail2 = transaction.getTransactionDetails().get(1);

							Account account1 = detail1.getAccount();
							Account account2 = detail2.getAccount();

							if (account1.equals(account2)) {
								JOptionPane.showMessageDialog(TransactionEditor.this,
										"A transaction with two transaction details for the same account cannot be converted into a transfer transaction.");

								return false;
							}
						}
					}
				} else if (TRANSFER_TRANSACTION_TAB == transactionEditorViewContainer.getSelectedIndex()) {
					TransferTransactionEditorView view = (TransferTransactionEditorView) transactionEditorViewContainer.getSelectedComponent();
					if (SIMPLE_TRANSACTION_TAB == newTabIndex) {
						Account fromAccount = (Account) view.getFromAccountComboBox().getSelectedItem();
						Account toAccount = (Account) view.getToAccountComboBox().getSelectedItem();

						if (null != fromAccount && null != toAccount) {
							JOptionPane.showMessageDialog(TransactionEditor.this,
									"A transfer transaction with a from and a to account selected cannot be converted into a simple transaction.");
							
							return false;
						}
					}
				}

				return true;
			}
		});

		transactionEditorViewContainer.addTab("simple", new SimpleTransactionEditorView(this));
		transactionEditorViewContainer.addTab("transfer", new TransferTransactionEditorView(this));
		transactionEditorViewContainer.addTab("complex", new ComplexTransactionEditorView(this));
	}

	protected AbstractTransactionEditorView getTransactionEditorView() {
		return (AbstractTransactionEditorView) getTransactionEditorViewContainer().getSelectedComponent();
	}

	private JComponent createButtonPanel() {
		JPanel panel = new JPanel(new GridLayout(1, 4, 5, 0));

		panel.add(new JButton(getNewAction()));
		panel.add(new JButton(getEditAction()));
		panel.add(new JButton(getEnterAction()));
		panel.add(new JButton(getCancelAction()));

		return panel;
	}

	protected Action getNewAction() {
		if (null == newAction)
			createNewAction();

		return newAction;
	}

	private void createNewAction() {
		newAction = new MSBAction(new ActionResourceDefinition(Resources.getInstance().getString("action.button.new"), "new"), getActionListener());
	}

	protected Action getEditAction() {
		if (null == editAction)
			createEditAction();

		return editAction;
	}

	private void createEditAction() {
		editAction = new MSBAction(new ActionResourceDefinition(Resources.getInstance().getString("action.button.edit"), "edit"), getActionListener());
		editAction.setEnabled(false);
	}

	protected Action getEnterAction() {
		if (null == enterAction)
			createEnterAction();

		return enterAction;
	}

	private void createEnterAction() {
		enterAction = new MSBAction(new ActionResourceDefinition(Resources.getInstance().getString("action.button.enter"), "enter"), getActionListener());
		enterAction.setEnabled(false);
	}

	protected Action getCancelAction() {
		if (null == cancelAction)
			createCancelAction();

		return cancelAction;
	}

	private void createCancelAction() {
		cancelAction = new MSBAction(new ActionResourceDefinition(Resources.getInstance().getString("action.button.cancel"), "cancel"), getActionListener());
		cancelAction.setEnabled(false);
	}

	protected ActionListener getActionListener() {
		if (null == actionListener)
			createActionListener();

		return actionListener;
	}

	private void createActionListener() {
		actionListener = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				String command = e.getActionCommand();
				assert null != command : "null command encountered in TransactionEditor ActionListener";

				if (command.equals("new"))
					createNewTransaction();
				else if (command.equals("edit"))
					doEdit();
				else if (command.equals("enter"))
					saveTransaction();
				else if (command.equals("cancel"))
					revertTransaction();
			}
		};
	}

	private void createNewTransaction() {
		if (isDirty())
			if (!canProceedAfterOfferToSaveTransaction())
				return;

		newTransaction = true;
		setTransaction(new Transaction());
		getTransactionEditorViewContainer().setSelectedIndex(SIMPLE_TRANSACTION_TAB);
		beginEditing();
	}

	private void beginEditing() {
		editing = true;
		getNewAction().setEnabled(false);
		getEditAction().setEnabled(false);
		getEnterAction().setEnabled(true);
		getCancelAction().setEnabled(true);
		
		for (int i = 0; i < 3; ++i)
			((AbstractTransactionEditorView) getTransactionEditorViewContainer().getComponentAt(i)).beginEditing();
	}

	private void endEditing() {
		editing = false;
		getNewAction().setEnabled(true);
		getEditAction().setEnabled(true);
		getEnterAction().setEnabled(false);
		getCancelAction().setEnabled(false);

		for (int i = 0; i < 3; ++i)
			((AbstractTransactionEditorView) getTransactionEditorViewContainer().getComponentAt(i)).endEditing();
	}

	boolean isEditing() {
		return editing;
	}

	private boolean isDirty() {
		return dirty;
	}

	void setDirty() {
		dirty = true;
	}

	private boolean canProceedAfterOfferToSaveTransaction() {
		int reply = JOptionPane.showConfirmDialog(this,
				"The current transaction has unsaved changes. Do you wish save them before editing a different transaction?", "Save Changes",
				JOptionPane.YES_NO_CANCEL_OPTION);
		
		if (JOptionPane.CANCEL_OPTION == reply)
			return false;
		else if (JOptionPane.OK_OPTION == reply)
			saveTransaction();

		return true;
	}

	private void doEdit() {
		beginEditing();
	}

	public void showTransaction(Transaction value) {
		newTransaction = false;
		setTransaction(value);
	}

	/**
	 * Changes the {@link Transaction} object being edited. A new {@link Transaction} object can be created by passing a {@code null} reference to this method.
	 * 
	 * @param value
	 *            The {@link Transaction} object to edit, or {@code null} to create a new {@link Transaction} object.
	 */
	protected void setTransaction(Transaction value) {
		transaction = value;

		for (int i = 0; i < getTransactionEditorViewContainer().getTabCount(); ++i) {
			AbstractTransactionEditorView view = (AbstractTransactionEditorView) getTransactionEditorViewContainer().getComponentAt(i);
			
			if (null == value)
				view.clearTransaction();
			else
				view.setTransaction(value);
		}
	}

	/**
	 * Commits the modifications made by the editor to the current {@link Transaction} object into the database.
	 */
	protected void saveTransaction() {
		endEditing();
		getTransactionEditorView().commitChanges();

		IDataManager dataManager = DataManagerFactory.getInstance().getDatabase().getDataManager();

		if (newTransaction)
			dataManager.persist(transaction);
		else
			dataManager.save(transaction);
	}

	/**
	 * Resets the values of the current {@link Transaction} object's fields to that of the persisted values in the database. This reverts any modifications made
	 * by the editor.
	 */
	protected void revertTransaction() {
		endEditing();

		if (newTransaction)
			setTransaction(null);
		else 
			revertExistingTransaction();
	}
	
	private void revertExistingTransaction() {
		IDataManager dataManager = DataManagerFactory.getInstance().getDatabase().getDataManager();
		dataManager.refresh(transaction);

		setTransaction(transaction);
	}

	public Account getAccount() {
		return account;
	}

	public void setAccount(Account value) {
		account = value;
	}
}
